package com.haohope.framework.datasource;

import java.beans.Transient;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.sql.DataSource;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.datasource.AbstractDataSource;
import org.springframework.jdbc.datasource.lookup.DataSourceLookup;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import com.alibaba.druid.pool.DruidDataSource;
import com.wb.common.Var;

public class RoutingDataSource extends AbstractDataSource implements InitializingBean , ApplicationContextAware{
	private static final Log LOG = LogFactory.getLog(RoutingDataSource.class);
	@Nullable
	private Map<Object, Object> targetDataSources;
	@Nullable
	private Object defaultTargetDataSource;
	private boolean lenientFallback = true;
	private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
	@Nullable
	private Map<Object, DataSource> resolvedDataSources;
	@Nullable
	private DataSource resolvedDefaultDataSource;
	private JdbcTemplate jdbcTemplate;
	private final String datasourcessql = " select * from wb_datasources";
	public RoutingDataSource(Object defaultTargetDataSource,Map<Object, Object> targetDataSources) {
		this.defaultTargetDataSource = defaultTargetDataSource;
		this.targetDataSources = targetDataSources;
	}
	public RoutingDataSource(Object defaultTargetDataSource,JdbcTemplate jdbcTemplate) {
		this.defaultTargetDataSource = defaultTargetDataSource;
		this.targetDataSources = new ConcurrentHashMap<Object, Object>();
		this.jdbcTemplate = jdbcTemplate;
	}
	public  final void setLenientFallback(boolean lenientFallback) {
		this.lenientFallback = lenientFallback;
	}
	public final  void afterPropertiesSet() {
		if (this.targetDataSources == null) {
			throw new IllegalArgumentException("Property 'targetDataSources' is required");
		}
		this.resolvedDataSources = new HashMap<>(this.targetDataSources.size());
		this.targetDataSources.forEach((key, value) -> {
			Object lookupKey = resolveSpecifiedLookupKey(key);
			DataSource dataSource = resolveSpecifiedDataSource(value);
			this.resolvedDataSources.put(lookupKey, dataSource);
		});
		if (this.defaultTargetDataSource == null) {
			throw new IllegalArgumentException("Property 'defaultTargetDataSource' is required");
		}
		this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
	}
	
	public Set<String> getDataSourceKeys(){
		Set<Object> _oldtitles = this.resolvedDataSources.keySet();
		Set<String> oldtitles = new HashSet<String>();
		for(Object obj:_oldtitles) {
			oldtitles.add(obj.toString());
		}
		return oldtitles;
	}
	//3.添加定时任务
    //@Scheduled(cron = "0/5 * * * * ?")
    //或直接指定时间间隔，例如：5秒
    //@Scheduled(fixedRate=5000)
    public static void dataSourceTask() {
    	if(null==APPLICATIONCONTEXT) {
			return ;
		}
		RoutingDataSource routingDataSource = APPLICATIONCONTEXT.getBean(RoutingDataSource.class);
		
		//routingDataSource.get
    	if(null!=routingDataSource.jdbcTemplate) {
    		try {
    			List<DataSourceBean> datasources = routingDataSource.jdbcTemplate.query(routingDataSource.datasourcessql,new DataSourceBeanRowMapper());
        		Set<String> newtitles = new HashSet<String>();
        		for(DataSourceBean datasource:datasources) {
        			String title = datasource.get_TITLE();
    				boolean disabled = datasource.is_DISABLED();
    				newtitles.add(title);
        			try {
        				if(!disabled) {
        					//testConnect(datasource.getUrl(), datasource.get_USERNAME(), datasource.get_PASSWORD());
        					routingDataSource.createDataSource(title, datasource.getUrl(), datasource.get_USERNAME(), datasource.get_PASSWORD());
        					try {
        	    				Var.set("sys.jndi."+title, "java:comp/env/jdbc/"+title,true);
        	    			}catch(Exception e) {
        	    				//e.printStackTrace();
        	    			}
        				}else if(routingDataSource.resolvedDataSources.containsKey(title)){
        					routingDataSource.breakConnect(title);
        				}
    				} catch (Exception e) {
    					e.printStackTrace();
    				}
        		}
        		//移除多余的
        		Set<String> oldtitles = routingDataSource.getDataSourceKeys();
        		oldtitles.removeAll(newtitles);
        		for(String obj:oldtitles) {
        			try {
        				routingDataSource.breakConnect(obj);
    				} catch (SQLException e) {
    					e.printStackTrace();
    				}
        			try {
    					Var.delVar("sys.jndi","[\""+obj+"\"]",true);
        			}catch(Exception e) {}
        		}
    		}catch(Exception e) {
    			e.printStackTrace();
    		}
    		
    	}
    }
    public static class DataSourceBeanRowMapper implements RowMapper<DataSourceBean>{
    	public DataSourceBeanRowMapper() {}
		@Override
		public DataSourceBean mapRow(ResultSet rs, int rowNum) throws SQLException {
			DataSourceBean dataSourceBean = new DataSourceBean();
			dataSourceBean.set_IP(rs.getString("IP"));
			dataSourceBean.set_PORT(rs.getInt("PORT"));
			dataSourceBean.set_USERNAME(rs.getString("USERNAME"));
			dataSourceBean.set_PASSWORD(rs.getString("PASSWORD"));
			dataSourceBean.set_DATABASETYPE(rs.getString("DATABASETYPE"));
			dataSourceBean.set_SCHEMANAME(rs.getString("SCHEMANAME"));
			dataSourceBean.set_TITLE(rs.getString("TITLE"));
			dataSourceBean.set_URLADDITION(rs.getString("URLADDITION"));
			dataSourceBean.set_DISABLED(rs.getBoolean("DISABLED"));
			return dataSourceBean;
		}
    }
    public static class DataSourceBean {
    	String _IP;
    	int _PORT;
    	String _USERNAME;
    	String _PASSWORD;
		String _DATABASETYPE;
    	String _SCHEMANAME;
    	String _TITLE;
    	String _URLADDITION;
    	boolean _DISABLED;
    	@Transient
    	public String getUrl() {
    		return DBType.valueOf(_DATABASETYPE).createUrl(_IP, _PORT, _SCHEMANAME, _URLADDITION);
    		/**
    		String result = null;
    		if (DBType.isMysql(_DATABASETYPE)) {
				// jdbc:mysql://127.0.0.1:3306/businessTrip?useUnicode=true&characterEncoding=utf8&useFastDateParsing=false
				result = "jdbc:mysql://" + get_IP() + ":" + get_PORT() + "/" + get_SCHEMANAME();
				if (_URLADDITION != null)
					result = result + "?" + _URLADDITION;
				else
					result += "?useUnicode=true&characterEncoding=utf8&useFastDateParsing=false&useSSL=false";
			} else if (DBType.isSqlserver(_DATABASETYPE)) {
				// jdbc:sqlserver://127.0.0.1:1433;databaseName=businessTrip
				result = "jdbc:sqlserver://" + get_IP() + ":" + get_PORT() + ";databaseName=" + get_SCHEMANAME();
			} else if (DBType.isOracle(_DATABASETYPE)) {
				// jdbc:oracle:thin:@192.168.99.72:1521:XE
				result = "jdbc:oracle:thin:@" + get_IP() + ":" + get_PORT() + ":" + get_SCHEMANAME();
			}
    		return result;
    		**/
    	}
		public String get_IP() {
			return _IP;
		}
		public void set_IP(String _IP) {
			this._IP = _IP;
		}
		public int get_PORT() {
			return _PORT;
		}
		public void set_PORT(int _PORT) {
			this._PORT = _PORT;
		}
		public String get_USERNAME() {
			return _USERNAME;
		}
		public void set_USERNAME(String _USERNAME) {
			this._USERNAME = _USERNAME;
		}
		public String get_PASSWORD() {
			return this._PASSWORD;
		}
		public void set_PASSWORD(String _PASSWORD) {
			this._PASSWORD = _PASSWORD;
		}
		public String get_DATABASETYPE() {
			return _DATABASETYPE;
		}
		public void set_DATABASETYPE(String _DATABASETYPE) {
			this._DATABASETYPE = _DATABASETYPE;
		}
		public String get_SCHEMANAME() {
			return _SCHEMANAME;
		}
		public void set_SCHEMANAME(String _SCHEMANAME) {
			this._SCHEMANAME = _SCHEMANAME;
		}
		public String get_TITLE() {
			return _TITLE;
		}
		public void set_TITLE(String _TITLE) {
			this._TITLE = _TITLE;
		}
		public String get_URLADDITION() {
			return _URLADDITION;
		}
		public void set_URLADDITION(String _URLADDITION) {
			this._URLADDITION = _URLADDITION;
		}
		public boolean is_DISABLED() {
			return _DISABLED;
		}
		public void set_DISABLED(boolean _DISABLED) {
			this._DISABLED = _DISABLED;
		}
    }
	protected final Object resolveSpecifiedLookupKey(Object lookupKey) {
		return lookupKey;
	}
	public void createDataSource(String key,String url,String user,String passwd) throws SQLException {
		if(null==key) {
			return ;
		}
    	if(this.resolvedDataSources.containsKey(key)) {
    		return ;
    	}
    	Lock lock = new ReentrantLock();
    	try {
    		lock.lock();
    		if(!this.resolvedDataSources.containsKey(key)){
    			RoutingDataSource.testConnect(url,user,passwd);
    			DruidDataSource dataSource = new DruidDataSource();
    			dataSource.setUrl(url);
    			//dataSource.setDriverClassName(driverClass);
    			dataSource.setName(key);
    			dataSource.setUsername(user);
    			dataSource.setPassword(passwd);
    			DataSourceBuilder.configFromPropety(dataSource, System.getProperties());
    			/**
    			dataSource.setFilters("stat,wall,log4j,config");
    			dataSource.setInitialSize(this.druidConfig.getInitialSize());// #初始连接数
    			dataSource.setMinIdle(this.druidConfig.getMinIdle());// #配置0,当线程池数量不足，自动补充。
    			dataSource.setMaxActive(this.druidConfig.getMaxActive());// #最大连接池数量
    			dataSource.setMaxWait(this.druidConfig.getMaxWait());// #获取链接超时时间为1分钟，单位为毫秒。
    			// dataSource.setValidationQuery(fdatasource.getValidationQuery());
    			// //不用设置，用执行ping命令检测心跳
    			dataSource.setTestOnBorrow(this.druidConfig.isTestOnBorrow());
    			dataSource.setTestOnReturn(this.druidConfig.isTestOnReturn());
    			dataSource.setTestWhileIdle(this.druidConfig.isTestWhileIdle());
    			dataSource.setTimeBetweenEvictionRunsMillis(this.druidConfig.getTimeBetweenEvictionRunsMillis());// #1.Destroy线程会检测连接的间隔时间
    			dataSource.setMinEvictableIdleTimeMillis(this.druidConfig.getMinEvictableIdleTimeMillis());// #一个链接生存的时间,半个小时
    			dataSource.setRemoveAbandoned(this.druidConfig.isRemoveAbandoned());// #链接使用超过时间限制是否回收
    			dataSource.setRemoveAbandonedTimeout(this.druidConfig.getRemoveAbandonedTimeout());// #超过时间限制时间（单位秒），目前为5分钟，如果有业务处理时间超过5分钟，可以适当调整。
    			dataSource.setLogAbandoned(this.druidConfig.isLogAbandoned());// #链接回收的时候控制台打印信息，测试环境可以加上true，线上环境false。会影响性能。
    			dataSource.setConnectProperties(this.druidConfig.getConnectProperties());
    			**/
    			dataSource.init();
    			this.resolvedDataSources.put(key, dataSource);
    			//return this.resolvedDataSources.get(key);
    		}else {
    			//return this.resolvedDataSources.get(key);
    		}
    	}finally {
    		lock.unlock();
    	}
    }
	public final void breakConnect(String key) throws SQLException{
		try {
			if(this.resolvedDataSources.containsKey(key)) {
				DataSource datasource = this.resolvedDataSources.get(key);
				if(datasource instanceof DruidDataSource) {
					((DruidDataSource)datasource).close();
				}
				LOG.info("-----------------------------breakConnect "+ key + " success.");
			}else {
				LOG.info("-----------------------------"+ key + " is not exist.");
			}
		}catch(Exception e) {
			LOG.error("-----------------------------breakConnect "+ key + " fail : "+ e.getMessage());
			throw new SQLException(e);
		}
	}
	public static void testConnect(String url,String user,String passwd) throws SQLException{
		int num = 0;
		Exception exception = null;
		try {
			Class.forName("com.mysql.jdbc.Driver");
		}catch (ClassNotFoundException e) {
			//throw new SQLException(e);
			num ++;
			exception = e;
		}
		try {
			Class.forName("com.mysql.cj.jdbc.Driver");
		}catch (ClassNotFoundException e) {
			//throw new SQLException(e);
			num ++;
			exception = e;
		}
		try {
			Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
		}catch (ClassNotFoundException e) {
			//throw new SQLException(e);
			num ++;
			exception = e;
		}
		try {
			Class.forName("oracle.jdbc.driver.OracleDriver");
		}catch (ClassNotFoundException e) {
			//throw new SQLException(e);
			num ++;
			exception = e;
		}
		if(4==num) {
			throw new SQLException(exception);
		}
		Connection conn = null;
		try {
			conn = DriverManager.getConnection(url, user, passwd);
		} catch (SQLException e) {
			throw e;
		} finally {
			if (conn != null)
				try {
					conn.close();
				} catch (SQLException e) {
					e.printStackTrace();
				}
		}
	}
	protected  final  DataSource resolveSpecifiedDataSource(Object dataSource) throws IllegalArgumentException {
		if (dataSource instanceof DataSource) {
			return (DataSource) dataSource;
		}
		else if (dataSource instanceof String) {
			return this.dataSourceLookup.getDataSource((String) dataSource);
		}
		else {
			throw new IllegalArgumentException(
					"Illegal data source value - only [javax.sql.DataSource] and String supported: " + dataSource);
		}
	}
	@Override
	public final Connection getConnection() throws SQLException {
		return determineTargetDataSource().getConnection();
	}
	@Override
	public  final Connection getConnection(String username, String password) throws SQLException {
		return determineTargetDataSource().getConnection(username, password);
	}
	@Override
	@SuppressWarnings("unchecked")
	public final  <T> T unwrap(Class<T> iface) throws SQLException {
		if (iface.isInstance(this)) {
			return (T) this;
		}
		return determineTargetDataSource().unwrap(iface);
	}
	@Override
	public final boolean isWrapperFor(Class<?> iface) throws SQLException {
		return (iface.isInstance(this) || determineTargetDataSource().isWrapperFor(iface));
	}
	protected final DataSource determineTargetDataSource() {
		Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
		Object lookupKey = determineCurrentLookupKey();
		LOG.info("druid-----------------------------lookupKey:"+lookupKey);
		DataSource dataSource = this.resolvedDataSources.get(lookupKey);
		if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
			LOG.info("druid-----------------------------default datasource");
			dataSource = this.resolvedDefaultDataSource;
		}
		if (dataSource == null) {
			throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
		}
		return dataSource;
	}
	@Nullable
	protected final Object determineCurrentLookupKey() {
		return THREAD_DATA_SOURCE_KEY.get();
	}
	/**
             * 注意：数据源标识保存在线程变量中，避免多线程操作数据源时互相干扰
     */
    private static final ThreadLocal<String> THREAD_DATA_SOURCE_KEY = new ThreadLocal<String>();
    //清除数据源类型
    public static void clearDataSourceKey() {
    	THREAD_DATA_SOURCE_KEY.remove();
    }
    //设置数据源类型
    public static void setDataSourceKey(String dataSourceKey) {
    	THREAD_DATA_SOURCE_KEY.set(dataSourceKey);
    }
    private static volatile ApplicationContext APPLICATIONCONTEXT;
    public static Set<String> dataSourceKeys(){
		if(null==APPLICATIONCONTEXT) {
			return new HashSet<String>();
		}
		RoutingDataSource routingDataSource = APPLICATIONCONTEXT.getBean(RoutingDataSource.class);
		Set<String> datas = routingDataSource.getDataSourceKeys();
		datas.add("default");
		return datas;
	}
	public static Connection connection() throws SQLException {
		if(null==APPLICATIONCONTEXT) {
			return null;
		}
		RoutingDataSource routingDataSource = APPLICATIONCONTEXT.getBean(RoutingDataSource.class);
		return routingDataSource.getConnection();
	}
	public static Connection connection(String key) throws SQLException {
		RoutingDataSource.setDataSourceKey(key);
		Connection conn = connection();
		RoutingDataSource.clearDataSourceKey();
		return conn;
	}
	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		APPLICATIONCONTEXT = applicationContext;
	}
}
